package kpi.javatraining6.gnatiuk.src.tester.readers;

import kpi.javatraining6.gnatiuk.src.tester.Logger;
import kpi.javatraining6.gnatiuk.src.tester.question.AnsweredQuestion;
import kpi.javatraining6.gnatiuk.src.tester.user.User;
import kpi.javatraining6.gnatiuk.src.tester.user.UserProgress;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class UserDOMParser {
    Document doc;
    XPath xpath;
    List<User> userList;
    Logger logger;

    public UserDOMParser() {
        logger = Logger.getInstance();
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = domFactory.newDocumentBuilder();
            doc = builder.parse("users.xml");
        } catch (ParserConfigurationException | SAXException | IOException e) {
            logger.recordExceptionalEvent(e);
            System.out.println("cant create parser");
            System.exit(1);
        }
        XPathFactory factory = XPathFactory.newInstance();
        xpath = factory.newXPath();
    }


    public List<User> getUsers() {
        if (userList == null) parseUsers();
        return userList;
    }

    private void parseUsers() {
        userList = new ArrayList<>();
        String xPathExpression = "//user";
        try {
            XPathExpression  expr = xpath.compile(xPathExpression);
            NodeList users = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
            for (int userIndex = 1; userIndex <= users.getLength(); userIndex++) {
                String userName = parseUser(userIndex);
                String password = parsePassword(userIndex);
                HashMap<String, UserProgress> userHistoryMap = parseMap(userIndex);
               User someUser = new User(userName, password, userHistoryMap);
               userList.add(someUser);
            }
        } catch (XPathExpressionException e) {
            System.out.println("cant parse user");
            logger.recordExceptionalEvent(e);
            System.exit(1);
        }
    }

    private HashMap<String, UserProgress> parseMap(int userIndex) throws XPathExpressionException {
        String xmlExpression = String.format("//user[%d]/userProgressMap/progressItem", userIndex);
        XPathExpression expr = xpath.compile(xmlExpression);
        NodeList topics = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
        HashMap<String, UserProgress> resultMap = new HashMap<>();
        for (int topicIndex = 1; topicIndex <= topics.getLength(); topicIndex++) {
            String topicName = parseTopic(userIndex, topicIndex);
            ArrayList<AnsweredQuestion> answeredQuestions = parseQuestions(userIndex, topicIndex);
            resultMap.put(topicName, new UserProgress(answeredQuestions));
        }
        return resultMap;
    }

    private ArrayList<AnsweredQuestion> parseQuestions(int userIndex, int topicIndex) throws XPathExpressionException {
        String xmlExpression = String.format("//user[%d]/userProgressMap/progressItem[%d]/questionsList/question", userIndex, topicIndex);
        XPathExpression expr = xpath.compile(xmlExpression);
        NodeList questions = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
        ArrayList<AnsweredQuestion> resultList = new ArrayList<>();
        for (int questionIndex = 1; questionIndex <= questions.getLength(); questionIndex++) {
            AnsweredQuestion answeredQuestion = parseAnsweredQuestion(questionIndex, userIndex, topicIndex);
            resultList.add(answeredQuestion);
        }
        return resultList;
    }

    private AnsweredQuestion parseAnsweredQuestion(int questionIndex, int userIndex, int topicIndex) {
         String question = parseQuestion(questionIndex, userIndex, topicIndex);
         boolean isRightAnswer = parseAnswer(questionIndex, userIndex, topicIndex);
         return new AnsweredQuestion(question, isRightAnswer);
    }

    private boolean parseAnswer(int questionIndex, int userIndex, int topicIndex) {
        String xmlExpression = String.format("//user[%d]/userProgressMap/progressItem[%d]/questionsList/question[%d]/@isCorrectAnswer", userIndex, topicIndex, questionIndex);
        boolean result = false;
        try {
            XPathExpression expr = xpath.compile(xmlExpression);
            result = (boolean)expr.evaluate(doc, XPathConstants.BOOLEAN);
        } catch (XPathExpressionException e) {
            System.out.println("cant parse attribute");
            logger.recordExceptionalEvent(e);
            System.exit(1);
        }
        return result;
    }

    private String parseQuestion(int questionIndex, int userIndex, int topicIndex) {
        String xmlExpression = String.format("//user[%d]/userProgressMap/progressItem[%d]/questionsList/question[%d]/text()", userIndex, topicIndex, questionIndex);
        return parseString(xmlExpression);
    }

    private String parseTopic(int userIndex, int topicIndex) {
        String xmlExpression = String.format("//user[%d]/userProgressMap/progressItem[%d]/topicName", userIndex, topicIndex);
        return parseString(xmlExpression);
    }

    private String parsePassword(int userIndex) {
        String xmlExpression = String.format("//user[%d]/password/text()", userIndex);
        return parseString(xmlExpression);
    }

    private String parseUser(int userIndex) {
        String xmlExpression = String.format("//user[%d]/userName/text()", userIndex);
        return parseString(xmlExpression);
    }

    private String parseString(String xPathExpression) {
        String result = "";
        try {
            XPathExpression expr = xpath.compile(xPathExpression);
            result = (String)expr.evaluate(doc, XPathConstants.STRING);
        } catch (XPathExpressionException e) {
            System.out.println("cant parse string");
            logger.recordExceptionalEvent(e);
            System.exit(1);
        }
        return result;
    }
}
